// --- Copyright (c) 2006-2008 Stefan Kyntchev ---
// This software is written and copyrighted by Stefan Kyntchev 
// and BeyondPod Team members. All rights are reserved.
// Author contact: support@beyondpod.mobi
// ------------------------------------------------------------------------
// This file is part of BeyondPod RSS Feed Reader and Podcast manager
// (www.codeplex.com/BeyondPod) 
// BeyondPod is free software: you can redistribute it and/or modify 
// it under the terms of the GNU General Public License as published by 
// the Free Software Foundation, either version 3 of the License, or 
// (at your option) any later version. 
//  
// BeyondPod is distributed in the hope that it will be useful, 
// but WITHOUT ANY WARRANTY; without even the implied warranty of 
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
// GNU General Public License for more details. 
//  
// You should have received a copy of the GNU General Public License
// along with BeyondPod. If not, see <http://www.gnu.org/licenses/>
// ------------------------------------------------------------------------
// COPYRIGHT NOTICE: This copyright notice may NOT be removed, obscured or modified 
// without written consent from the author.
// --- End copyright notice --- 


using System;
using System.Collections.Generic;
using System.Net;
using System.Windows.Forms;
using svs.Mobile.BeyondMedia.FeedCore.Properties;
using svs.Mobile.BeyondMedia.FeedCore.RSS.Entities;

namespace svs.Mobile.BeyondMedia.FeedCore.RSS
{
	public static class EnclosureDownloadManager
	{
		#region Delegates

		public delegate void DownloadEvent();

		public delegate void EnclosureDownloadEvent(Track t);

		public delegate void EnclosureDownloadStartingEvent(Track t, bool isRetrying);

		#endregion

		private static readonly DownloadStatisticsList _DownloadStats = new DownloadStatisticsList();

		private static readonly EnclosureDownloader m_Downloader = new EnclosureDownloader();
		private static readonly EnclosureDownloadQueue m_DownloadQueue = new EnclosureDownloadQueue();
		private static readonly Dictionary<string, int> m_RetryList = new Dictionary<string, int>();
		private static bool m_AtQueueStart = false;

		#region Enclosure Queue

		private class EnclosureDownloadQueue : Queue<KeyValuePair<Feed, RssEnclosure>>
		{
			public int DownloadCount = 0;

			public void Enqueue(List<KeyValuePair<Feed, RssEnclosure>> items)
			{
				foreach (KeyValuePair<Feed, RssEnclosure> pair in items)
				{
					Enqueue(pair);
				}
			}

			public new void Enqueue(KeyValuePair<Feed, RssEnclosure> item)
			{
				if (Contains(item))
				{
					/*MessageBox.Show(Resources.EnclosureAlreadyInQueueMsg, Resources.EnclosureAlreadyInQueueTitle,
					                MessageBoxButtons.OK,
					                MessageBoxIcon.Exclamation, MessageBoxDefaultButton.Button1);*/
					return;
				}

				if (Count == 0)
					DownloadCount = 1;
				else
					DownloadCount += 1;

				base.Enqueue(item);
			}
		}

		#endregion

		static EnclosureDownloadManager()
		{
			m_Downloader.EnclosureDownloadCompleted += OnDownloadCompleted;
			m_Downloader.EnclosureDownloadFailed += OnDownloadFailed;
			m_Downloader.EnclosureDownloadProgress += OnDownloadProgress;
			m_Downloader.EnclosureDownloadTerminated += OnDownloadTerminated;
			m_Downloader.EnclosureDownloadStarted += OnDownloadStarted;
		}

		#region Public Methods

		/// <summary>
		/// Invoked when trying to resume a partially downloaded track
		/// </summary>
		/// <param name="track"></param>
		public static void DownloadEnclosure(Track track)
		{
			if (!PrepareForDownload())
				return;

			RssEnclosure temp = new RssEnclosure(track.Url, track.DownloadSize);
			KeyValuePair<Feed, RssEnclosure> toDownload = new KeyValuePair<Feed, RssEnclosure>(track.ParentFeed, temp);

			m_DownloadQueue.Enqueue(toDownload);
			DownloadQueuedEnclosures();
		}

		/// <summary>
		/// Invoked when downloading a single or group of enclosures from the Content view
		/// </summary>
		/// <param name="todownload"></param>
		public static void DownloadEnclosures(List<KeyValuePair<Feed, RssEnclosure>> todownload)
		{
			//If downloading just add it to the queue
			if (m_Downloader.IsDownloading && m_DownloadQueue.Count > 0)
			{
				m_DownloadQueue.Enqueue(todownload);
				OnEnclosureDownloadStarting(null);
				return;
			}

			if (!PrepareForDownload())
				return;

			m_DownloadQueue.Enqueue(todownload);
			DownloadQueuedEnclosures();
		}

		public static void TerminateAllDownloads()
		{
			if (m_Downloader.IsDownloading)
				m_Downloader.Stop();

			if (m_DownloadQueue.Count > 0)
			{
				m_DownloadQueue.Clear();
			}

			//Save the download history
			EnclosureDownloadHistory.SaveHistory();

			if (DownloadBatchCanceled != null)
				DownloadBatchCanceled();
		}

		#endregion

		#region Public Properties

		//True if the current download is the first download for the queue
		/// <summary>
		/// The current feed that is being downloaded
		/// </summary>
		public static Feed CurrentFeed
		{
			get
			{
				if (m_DownloadQueue.Count == 0)
					return null;
				else
					return m_DownloadQueue.Peek().Key;
			}
		}

		public static bool IsAtQueueStart
		{
			get { return m_AtQueueStart; }
		}

		public static bool IsDownloading
		{
			get { return m_Downloader.IsDownloading; }
		}

		/// <summary>
		/// Statistics for the last update
		/// </summary>
		public static DownloadStatisticsList Statistics
		{
			get { return _DownloadStats; }
		}

		/// <summary>
		/// Number of feeds that failed to download even after retry
		/// </summary>
		public static int FailedDownloadsCount
		{
			get
			{
				int failed = 0;
				foreach (KeyValuePair<string, int> pair in m_RetryList)
				{
					if (pair.Value <= 0)
						failed++;
				}

				return failed;
			}
		}

		/// <summary>
		/// Number of files to download
		/// </summary>
		public static int DownloadCount
		{
			get { return m_DownloadQueue.DownloadCount; }
		}

		public static int QueueCount
		{
			get { return m_DownloadQueue.Count; }
		}

		#endregion

		#region Private Methods

		private static void DownloadEnclosureInternal(Feed feed, string Url, string mimetype, DateTime? pubDate, long? downloadSize)
		{
			if (feed == null || string.IsNullOrEmpty(Url))
			{
				throw new ArgumentException("Error downloading enclosure: both Feed and Uri are required parameters");
			}

			//Clean before a download and make space for the new feed
			FeedRepository.DeleteOldTracksForFeed(feed, true);

			Track download = FeedRepository.FindOrCreateLocalTrack(feed, Url, mimetype, pubDate);

			if (download != null)
			{
				OnEnclosureDownloadStarting(download);
				download.DownloadSize = downloadSize ?? 0;

				m_Downloader.Track = download;
				m_Downloader.Start();
			}
		}

		private static void DownloadQueuedEnclosures()
		{
			if (IsAtQueueStart)
				OnDownloadBatchStarted();

			if (m_DownloadQueue.Count > 0)
			{
				KeyValuePair<Feed, RssEnclosure> current = m_DownloadQueue.Peek();
				DownloadEnclosureInternal(current.Key, current.Value.Url, current.Value.Type, current.Value.PubDate, current.Value.Length);
			}
			else
				OnDownloadBatchCompleted();
		}

		private static void OnEnclosureDownloadStarting(Track t)
		{
			bool isRetrying = false;
			if (t != null && m_RetryList.ContainsKey(t.Url))
				isRetrying = true;

			if (EnclosureDownloadStarting != null)
				EnclosureDownloadStarting(t, isRetrying);
		}

		private static bool PrepareForDownload()
		{
			if(!CoreHelper.IsDeviceConnected())
			{
				MessageBox.Show(Resources.DeviceNotConnectedMsg, Resources.DeviceNotConnectedTitle,
												MessageBoxButtons.OK,
												MessageBoxIcon.Exclamation, MessageBoxDefaultButton.Button1);
				return false;
			}

			if (m_Downloader.IsDownloading || RssFeedUpdateManager.IsUpdating)
			{
				MessageBox.Show(Resources.AnotherDownloadInProgressMsg, Resources.AnotherDownloadInProgressTitle,
				                MessageBoxButtons.OK,
				                MessageBoxIcon.Exclamation, MessageBoxDefaultButton.Button1);
				return false;
			}
			else
			{
				m_RetryList.Clear();
				m_DownloadQueue.Clear();
				m_AtQueueStart = true;
				return true;
			}
		}

		private static void OnDownloadCompleted(Track t)
		{
			UpdateStatistics(t);

			//In case we suceeded after a retry
			if (m_RetryList.ContainsKey(t.Url))
				m_RetryList.Remove(t.Url);

			t.LoadTrackNameFromFile();
			t.ParentFeed.NumDownloadedEnclosures++;

			if (EnclosureDownloadCompleted != null)
				EnclosureDownloadCompleted(t);

			m_DownloadQueue.Dequeue();
			DownloadQueuedEnclosures();
		}

		private static void UpdateStatistics(Track t)
		{
			if (m_Downloader.Statistics != null)
			{
				m_Downloader.Statistics.OwnerTrack = t;
				m_Downloader.Statistics.OwnerFeed = t.ParentFeed;
				_DownloadStats.Add(m_Downloader.Statistics);
			}
		}

		private static void OnDownloadStarted(Track t)
		{
			if (EnclosureDownloadStarted != null)
				EnclosureDownloadStarted(t);
		}

		private static void OnDownloadTerminated(Track t)
		{
			if (EnclosureDownloadTerminated != null)
				EnclosureDownloadTerminated(t);
		}

		private static void OnDownloadProgress(Track t)
		{
			if (m_RetryList.ContainsKey(t.Url))
			{
				//If the track has recovered - clear the retry count for this track - this way it can retry multiple times
				m_RetryList.Remove(t.Url);
			}

			if (EnclosureDownloadProgress != null)
				EnclosureDownloadProgress(t);
		}

		private static void OnDownloadFailed(Track t, Exception ex)
		{
			UpdateStatistics(t);

			if (!ShouldRetry(t, ex))
			{
				m_DownloadQueue.Dequeue();

				//Delete tracks that had nothing downloaded (connection error)
				if (!t.Exists)
					FeedRepository.DeleteTrack(t, true);
			}

			if (EnclosureDownloadFailed != null)
				EnclosureDownloadFailed(t);

			if (ex is CoreHelper.UnableToEstablishNetworkConnectionException)
				TerminateAllDownloads();
			else
				DownloadQueuedEnclosures();
		}

		private static bool ShouldRetry(Track t, Exception ex )
		{
			bool retry = true;

			if (ex is OutOfMemoryException)
				retry = false;
			
			if (ex is CoreHelper.IssuficientStorageSpaceException)
				retry = false;

			if(ex is WebException)
			{
				WebException e = ex as WebException;
				if (e.Status == WebExceptionStatus.ProtocolError ||
					e.Status == WebExceptionStatus.ProxyNameResolutionFailure ||
					e.Status == WebExceptionStatus.ServerProtocolViolation || e.Status == WebExceptionStatus.TrustFailure)
					retry = false;
			}

			if (retry)
			{
				if (m_RetryList.ContainsKey(t.Url))
				{
					m_RetryList[t.Url] -= 1;
				}
				else
				{
					m_RetryList[t.Url] = 1;
				}
			}
			else
			{
				m_RetryList[t.Url] = 0; //indicate that we have a failed download
			}

			return m_RetryList[t.Url] > 0;
		}

		private static void OnDownloadBatchStarted()
		{
			_DownloadStats.Clear();
			_DownloadStats.DownloadTime = DateTime.Now;
			m_AtQueueStart = false;
		}

		private static void OnDownloadBatchCompleted()
		{
			//Save the download history
			EnclosureDownloadHistory.SaveHistory();

			if (DownloadBatchCompleted != null)
				DownloadBatchCompleted();
		}

		#endregion

		public static event DownloadEvent DownloadBatchCompleted;
		public static event DownloadEvent DownloadBatchCanceled;

		public static event EnclosureDownloadEvent EnclosureDownloadCompleted;
		public static event EnclosureDownloadEvent EnclosureDownloadFailed;
		public static event EnclosureDownloadEvent EnclosureDownloadProgress;
		public static event EnclosureDownloadEvent EnclosureDownloadTerminated;
		public static event EnclosureDownloadEvent EnclosureDownloadStarted;
		public static event EnclosureDownloadStartingEvent EnclosureDownloadStarting;
	}
}